{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "5760f3c8",
   "metadata": {},
   "source": [
    "## Tabular Income Classifier Production Deployment\n",
    "\n",
    "To run this notebook you need the inference data. This can be acquired in two ways:\n",
    "\n",
    "  * Run `make train` or,\n",
    "  * `gsutil cp -R gs://seldon-models/scv2/examples/income/infer-data .`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "3af6ea59",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import json\n",
    "import requests"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "830f0b2f",
   "metadata": {},
   "outputs": [],
   "source": [
    "with open('./infer-data/test.npy', 'rb') as f:\n",
    "    x_ref = np.load(f)\n",
    "    x_h1 = np.load(f)\n",
    "    y_ref = np.load(f)\n",
    "    x_outlier = np.load(f)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "346cc929",
   "metadata": {},
   "outputs": [],
   "source": [
    "reqJson = json.loads('{\"inputs\":[{\"name\":\"input_1\",\"data\":[],\"datatype\":\"FP32\",\"shape\":[]}]}')\n",
    "url = \"http://0.0.0.0:9000/v2/models/model/infer\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "a7339d3d",
   "metadata": {},
   "outputs": [],
   "source": [
    "def infer(resourceName: str, batchSz: int, requestType: str):\n",
    "    if requestType == \"outlier\":\n",
    "        rows = x_outlier[0:0+batchSz]\n",
    "    elif requestType == \"drift\":\n",
    "        rows = x_h1[0:0+batchSz]\n",
    "    else:\n",
    "        rows = x_ref[0:0+batchSz]\n",
    "    reqJson[\"inputs\"][0][\"data\"] = rows.flatten().tolist()\n",
    "    reqJson[\"inputs\"][0][\"shape\"] = [batchSz, rows.shape[1]]\n",
    "    headers = {\"Content-Type\": \"application/json\", \"seldon-model\":resourceName}\n",
    "    response_raw = requests.post(url, json=reqJson, headers=headers)\n",
    "    print(response_raw)\n",
    "    print(response_raw.json())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1f37d87e",
   "metadata": {},
   "source": [
    "### Pipeline with model, drift detector and outlier detector"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "9e5485c0",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "apiVersion: mlops.seldon.io/v1alpha1\n",
      "kind: Model\n",
      "metadata:\n",
      "  name: income-preprocess\n",
      "spec:\n",
      "  storageUri: \"gs://seldon-models/scv2/examples/mlserver_1.3.5/income/preprocessor\"\n",
      "  requirements:\n",
      "  - sklearn\n",
      "---\n",
      "apiVersion: mlops.seldon.io/v1alpha1\n",
      "kind: Model\n",
      "metadata:\n",
      "  name: income\n",
      "spec:\n",
      "  storageUri: \"gs://seldon-models/scv2/examples/mlserver_1.3.5/income/classifier\"\n",
      "  requirements:\n",
      "  - sklearn\n",
      "---\n",
      "apiVersion: mlops.seldon.io/v1alpha1\n",
      "kind: Model\n",
      "metadata:\n",
      "  name: income-drift\n",
      "spec:\n",
      "  storageUri: \"gs://seldon-models/scv2/examples/mlserver_1.3.5/income/drift-detector\"\n",
      "  requirements:\n",
      "    - mlserver\n",
      "    - alibi-detect\n",
      "---\n",
      "apiVersion: mlops.seldon.io/v1alpha1\n",
      "kind: Model\n",
      "metadata:\n",
      "  name: income-outlier\n",
      "spec:\n",
      "  storageUri: \"gs://seldon-models/scv2/examples/mlserver_1.3.5/income/outlier-detector\"\n",
      "  requirements:\n",
      "    - mlserver\n",
      "    - alibi-detect\n"
     ]
    }
   ],
   "source": [
    "!cat ../../models/income-preprocess.yaml\n",
    "!echo \"---\"\n",
    "!cat ../../models/income.yaml\n",
    "!echo \"---\"\n",
    "!cat ../../models/income-drift.yaml\n",
    "!echo \"---\"\n",
    "!cat ../../models/income-outlier.yaml"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "c31d41c1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{}\n",
      "{}\n",
      "{}\n",
      "{}\n"
     ]
    }
   ],
   "source": [
    "!seldon model load -f ../../models/income-preprocess.yaml\n",
    "!seldon model load -f ../../models/income.yaml\n",
    "!seldon model load -f ../../models/income-drift.yaml\n",
    "!seldon model load -f ../../models/income-outlier.yaml"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "cb503272",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[1;39m{}\u001b[0m\n",
      "\u001b[1;39m{}\u001b[0m\n",
      "\u001b[1;39m{}\u001b[0m\n",
      "\u001b[1;39m{}\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "!seldon model status income-preprocess -w ModelAvailable | jq .\n",
    "!seldon model status income -w ModelAvailable | jq .\n",
    "!seldon model status income-drift -w ModelAvailable | jq .\n",
    "!seldon model status income-outlier -w ModelAvailable | jq ."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "36300582",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "apiVersion: mlops.seldon.io/v1alpha1\r\n",
      "kind: Pipeline\r\n",
      "metadata:\r\n",
      "  name: income-production\r\n",
      "spec:\r\n",
      "  steps:\r\n",
      "    - name: income\r\n",
      "    - name: income-preprocess\r\n",
      "    - name: income-outlier\r\n",
      "      inputs:\r\n",
      "      - income-preprocess\r\n",
      "    - name: income-drift\r\n",
      "      batch:\r\n",
      "        size: 20\r\n",
      "  output:\r\n",
      "    steps:\r\n",
      "    - income\r\n",
      "    - income-outlier.outputs.is_outlier\r\n"
     ]
    }
   ],
   "source": [
    "!cat ../../pipelines/income.yaml"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "ba8bd865",
   "metadata": {},
   "outputs": [],
   "source": [
    "!seldon pipeline load -f ../../pipelines/income.yaml"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "50f88343",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\r\n",
      "  \"pipelineName\": \"income-production\",\r\n",
      "  \"versions\": [\r\n",
      "    {\r\n",
      "      \"pipeline\": {\r\n",
      "        \"name\": \"income-production\",\r\n",
      "        \"uid\": \"cifej8iufmbc73e5int0\",\r\n",
      "        \"version\": 1,\r\n",
      "        \"steps\": [\r\n",
      "          {\r\n",
      "            \"name\": \"income\"\r\n",
      "          },\r\n",
      "          {\r\n",
      "            \"name\": \"income-drift\",\r\n",
      "            \"batch\": {\r\n",
      "              \"size\": 20\r\n",
      "            }\r\n",
      "          },\r\n",
      "          {\r\n",
      "            \"name\": \"income-outlier\",\r\n",
      "            \"inputs\": [\r\n",
      "              \"income-preprocess.outputs\"\r\n",
      "            ]\r\n",
      "          },\r\n",
      "          {\r\n",
      "            \"name\": \"income-preprocess\"\r\n",
      "          }\r\n",
      "        ],\r\n",
      "        \"output\": {\r\n",
      "          \"steps\": [\r\n",
      "            \"income.outputs\",\r\n",
      "            \"income-outlier.outputs.is_outlier\"\r\n",
      "          ]\r\n",
      "        },\r\n",
      "        \"kubernetesMeta\": {}\r\n",
      "      },\r\n",
      "      \"state\": {\r\n",
      "        \"pipelineVersion\": 1,\r\n",
      "        \"status\": \"PipelineReady\",\r\n",
      "        \"reason\": \"created pipeline\",\r\n",
      "        \"lastChangeTimestamp\": \"2023-06-30T14:41:38.343754921Z\",\r\n",
      "        \"modelsReady\": true\r\n",
      "      }\r\n",
      "    }\r\n",
      "  ]\r\n",
      "}\r\n"
     ]
    }
   ],
   "source": [
    "!seldon pipeline status income-production -w PipelineReady | jq -M ."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "80f44ee9",
   "metadata": {},
   "source": [
    "Show predictions from reference set. Should not be drift or outliers."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "c644c816",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0 0 1 1 0 1 0 0 1 0 0 0 0 0 1 1 0 0 0 1]\n",
      "<Response [200]>\n",
      "{'model_name': '', 'outputs': [{'data': [0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1], 'name': 'predict', 'shape': [20, 1], 'datatype': 'INT64', 'parameters': {'content_type': 'np'}}, {'data': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'name': 'is_outlier', 'shape': [1, 20], 'datatype': 'INT64', 'parameters': {'content_type': 'np'}}]}\n"
     ]
    }
   ],
   "source": [
    "batchSz=20\n",
    "print(y_ref[0:batchSz])\n",
    "infer(\"income-production.pipeline\",batchSz,\"normal\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "0469f798",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "seldon.default.model.income-drift.outputs\tcifej9gfh5ss738i5br0\t{\"name\":\"is_drift\", \"datatype\":\"INT64\", \"shape\":[\"1\", \"1\"], \"parameters\":{\"content_type\":{\"stringParam\":\"np\"}}, \"contents\":{\"int64Contents\":[\"0\"]}}\r\n"
     ]
    }
   ],
   "source": [
    "!seldon pipeline inspect income-production.income-drift.outputs.is_drift"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "25a10e57",
   "metadata": {},
   "source": [
    "Show predictions from drift data. Should be drift and probably not outliers."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "79d986be",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0 0 1 1 0 1 0 0 1 0 0 0 0 0 1 1 0 0 0 1]\n",
      "<Response [200]>\n",
      "{'model_name': '', 'outputs': [{'data': [0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1], 'name': 'predict', 'shape': [20, 1], 'datatype': 'INT64', 'parameters': {'content_type': 'np'}}, {'data': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'name': 'is_outlier', 'shape': [1, 20], 'datatype': 'INT64', 'parameters': {'content_type': 'np'}}]}\n"
     ]
    }
   ],
   "source": [
    "batchSz=20\n",
    "print(y_ref[0:batchSz])\n",
    "infer(\"income-production.pipeline\",batchSz,\"drift\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "2191405f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "seldon.default.model.income-drift.outputs\tcifejaofh5ss738i5brg\t{\"name\":\"is_drift\", \"datatype\":\"INT64\", \"shape\":[\"1\", \"1\"], \"parameters\":{\"content_type\":{\"stringParam\":\"np\"}}, \"contents\":{\"int64Contents\":[\"1\"]}}\r\n"
     ]
    }
   ],
   "source": [
    "!seldon pipeline inspect income-production.income-drift.outputs.is_drift"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dc31b5d9",
   "metadata": {},
   "source": [
    "Show predictions from outlier data. Should be outliers and probably not drift."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "552ecc6a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0 0 1 1 0 1 0 0 1 0 0 0 0 0 1 1 0 0 0 1]\n",
      "<Response [200]>\n",
      "{'model_name': '', 'outputs': [{'data': [0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1], 'name': 'predict', 'shape': [20, 1], 'datatype': 'INT64', 'parameters': {'content_type': 'np'}}, {'data': [1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1], 'name': 'is_outlier', 'shape': [1, 20], 'datatype': 'INT64', 'parameters': {'content_type': 'np'}}]}\n"
     ]
    }
   ],
   "source": [
    "batchSz=20\n",
    "print(y_ref[0:batchSz])\n",
    "infer(\"income-production.pipeline\",batchSz,\"outlier\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "c123e82f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "seldon.default.model.income-drift.outputs\tcifejb8fh5ss738i5bs0\t{\"name\":\"is_drift\", \"datatype\":\"INT64\", \"shape\":[\"1\", \"1\"], \"parameters\":{\"content_type\":{\"stringParam\":\"np\"}}, \"contents\":{\"int64Contents\":[\"0\"]}}\r\n"
     ]
    }
   ],
   "source": [
    "!seldon pipeline inspect income-production.income-drift.outputs.is_drift"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "289a3e39",
   "metadata": {},
   "source": [
    "### Explanations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "cb81943e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "apiVersion: mlops.seldon.io/v1alpha1\r\n",
      "kind: Model\r\n",
      "metadata:\r\n",
      "  name: income-explainer\r\n",
      "spec:\r\n",
      "  storageUri: \"gs://seldon-models/scv2/examples/mlserver_1.3.5/income/explainer\"\r\n",
      "  explainer:\r\n",
      "    type: anchor_tabular\r\n",
      "    modelRef: income\r\n"
     ]
    }
   ],
   "source": [
    "!cat ../../models/income-explainer.yaml"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "f7ac528e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{}\r\n"
     ]
    }
   ],
   "source": [
    "!seldon model load -f ../../models/income-explainer.yaml"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "e4a15796",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[1;39m{}\u001b[0m\r\n"
     ]
    }
   ],
   "source": [
    "!seldon model status income-explainer -w ModelAvailable | jq ."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "029bfa8b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0]\n",
      "<Response [200]>\n",
      "{'model_name': 'income-explainer_1', 'model_version': '1', 'id': 'cdd68ba5-c569-4930-886f-fbdc26e24866', 'parameters': {}, 'outputs': [{'name': 'explanation', 'shape': [1, 1], 'datatype': 'BYTES', 'parameters': {'content_type': 'str'}, 'data': ['{\"meta\": {\"name\": \"AnchorTabular\", \"type\": [\"blackbox\"], \"explanations\": [\"local\"], \"params\": {\"seed\": 1, \"disc_perc\": [25, 50, 75], \"threshold\": 0.95, \"delta\": 0.1, \"tau\": 0.15, \"batch_size\": 100, \"coverage_samples\": 10000, \"beam_size\": 1, \"stop_on_first\": false, \"max_anchor_size\": null, \"min_samples_start\": 100, \"n_covered_ex\": 10, \"binary_cache_size\": 10000, \"cache_margin\": 1000, \"verbose\": false, \"verbose_every\": 1, \"kwargs\": {}}, \"version\": \"0.9.1\"}, \"data\": {\"anchor\": [\"Marital Status = Never-Married\", \"Relationship = Own-child\", \"Capital Gain <= 0.00\"], \"precision\": 0.9942028985507246, \"coverage\": 0.0657, \"raw\": {\"feature\": [3, 5, 8], \"mean\": [0.7914951989026063, 0.9400749063670412, 0.9942028985507246], \"precision\": [0.7914951989026063, 0.9400749063670412, 0.9942028985507246], \"coverage\": [0.3043, 0.069, 0.0657], \"examples\": [{\"covered_true\": [[30, 0, 1, 1, 0, 1, 1, 0, 0, 0, 50, 2], [49, 4, 2, 1, 6, 0, 4, 1, 0, 0, 60, 9], [39, 2, 5, 1, 5, 0, 4, 1, 0, 0, 40, 9], [33, 4, 2, 1, 5, 0, 4, 1, 0, 0, 40, 9], [63, 4, 1, 1, 8, 1, 4, 0, 0, 0, 40, 9], [23, 4, 1, 1, 7, 1, 4, 1, 0, 0, 66, 8], [45, 4, 1, 1, 8, 0, 1, 1, 0, 0, 40, 1], [54, 4, 1, 1, 8, 4, 4, 1, 0, 0, 45, 9], [32, 6, 1, 1, 8, 4, 2, 0, 0, 0, 30, 9], [40, 5, 1, 1, 2, 0, 4, 1, 0, 0, 40, 9]], \"covered_false\": [[57, 4, 5, 1, 5, 0, 4, 1, 0, 1977, 45, 9], [53, 0, 5, 1, 0, 1, 4, 0, 8614, 0, 35, 9], [37, 4, 1, 1, 5, 0, 4, 1, 0, 0, 45, 9], [53, 4, 5, 1, 8, 0, 4, 1, 0, 1977, 55, 9], [35, 4, 1, 1, 8, 0, 4, 1, 7688, 0, 50, 9], [32, 4, 1, 1, 5, 1, 4, 1, 0, 0, 40, 9], [42, 4, 1, 1, 5, 0, 4, 1, 99999, 0, 40, 9], [32, 4, 1, 1, 8, 0, 4, 1, 15024, 0, 50, 9], [53, 7, 5, 1, 8, 0, 4, 1, 0, 0, 42, 9], [52, 1, 1, 1, 8, 0, 4, 1, 0, 0, 45, 9]], \"uncovered_true\": [], \"uncovered_false\": []}, {\"covered_true\": [[52, 7, 5, 1, 5, 3, 4, 1, 0, 0, 40, 9], [27, 4, 1, 1, 8, 3, 4, 1, 0, 0, 40, 9], [28, 4, 1, 1, 6, 3, 4, 1, 0, 0, 60, 9], [46, 6, 5, 1, 2, 3, 4, 1, 0, 0, 50, 9], [53, 2, 5, 1, 5, 3, 2, 0, 0, 1669, 35, 9], [27, 4, 5, 1, 8, 3, 4, 0, 0, 0, 40, 9], [25, 4, 1, 1, 8, 3, 4, 0, 0, 0, 40, 9], [29, 6, 5, 1, 2, 3, 4, 1, 0, 0, 30, 9], [64, 0, 1, 1, 0, 3, 4, 1, 0, 0, 50, 9], [63, 0, 5, 1, 0, 3, 4, 1, 0, 0, 30, 9]], \"covered_false\": [[50, 5, 1, 1, 8, 3, 4, 1, 15024, 0, 60, 9], [45, 6, 1, 1, 6, 3, 4, 1, 14084, 0, 45, 9], [37, 4, 1, 1, 8, 3, 4, 1, 15024, 0, 40, 9], [33, 4, 1, 1, 8, 3, 4, 1, 15024, 0, 60, 9], [41, 6, 5, 1, 8, 3, 4, 1, 7298, 0, 70, 9], [42, 6, 1, 1, 2, 3, 4, 1, 15024, 0, 60, 9]], \"uncovered_true\": [], \"uncovered_false\": []}, {\"covered_true\": [[41, 4, 1, 1, 1, 3, 4, 1, 0, 0, 40, 9], [55, 2, 5, 1, 8, 3, 4, 1, 0, 0, 50, 9], [35, 4, 5, 1, 5, 3, 4, 0, 0, 0, 32, 9], [31, 4, 1, 1, 2, 3, 4, 1, 0, 0, 40, 9], [47, 4, 1, 1, 1, 3, 4, 1, 0, 0, 40, 9], [33, 4, 5, 1, 5, 3, 4, 1, 0, 0, 40, 9], [58, 0, 1, 1, 0, 3, 4, 0, 0, 0, 50, 9], [44, 6, 1, 1, 2, 3, 4, 1, 0, 0, 90, 9], [30, 4, 1, 1, 6, 3, 4, 1, 0, 0, 40, 9], [25, 4, 1, 1, 5, 3, 4, 1, 0, 0, 40, 9]], \"covered_false\": [], \"uncovered_true\": [], \"uncovered_false\": []}], \"all_precision\": 0, \"num_preds\": 1000000, \"success\": true, \"names\": [\"Marital Status = Never-Married\", \"Relationship = Own-child\", \"Capital Gain <= 0.00\"], \"prediction\": [0], \"instance\": [47.0, 4.0, 1.0, 1.0, 1.0, 3.0, 4.0, 1.0, 0.0, 0.0, 40.0, 9.0], \"instances\": [[47.0, 4.0, 1.0, 1.0, 1.0, 3.0, 4.0, 1.0, 0.0, 0.0, 40.0, 9.0]]}}}']}]}\n"
     ]
    }
   ],
   "source": [
    "batchSz=1\n",
    "print(y_ref[0:batchSz])\n",
    "infer(\"income-explainer\",batchSz,\"normal\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "71019755",
   "metadata": {},
   "source": [
    "### Cleanup"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "a2d69d25",
   "metadata": {},
   "outputs": [],
   "source": [
    "!seldon pipeline unload income-production\n",
    "!seldon model unload income-preprocess\n",
    "!seldon model unload income\n",
    "!seldon model unload income-drift\n",
    "!seldon model unload income-outlier\n",
    "!seldon model unload income-explainer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ba3ba4ba",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
